<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>高级拓扑图连线系统</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            font-family: Arial, sans-serif;
            overflow: hidden;
        }

        #container {
            width: 100vw;
            height: 100vh;
            position: relative;
            background-color: #f5f5f5;
        }

        .node {
            position: absolute;
            width: 80px;
            height: 80px;
            border-radius: 4px;
            background-color: #fff;
            border: 2px solid #3498db;
            display: flex;
            justify-content: center;
            align-items: center;
            cursor: move;
            user-select: none;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }

        .node.selected {
            border-color: #e74c3c;
            box-shadow: 0 0 10px rgba(231, 76, 60, 0.5);
        }

        .port {
            position: absolute;
            width: 10px;
            height: 10px;
            background-color: #2ecc71;
            border-radius: 50%;
            cursor: crosshair;
            z-index: 10;
        }

        .port:hover {
            transform: scale(1.5);
            background-color: #e74c3c;
        }

        .connection {
            position: absolute;
            pointer-events: stroke;
            stroke: #7f8c8d;
            stroke-width: 3;
            fill: none;
            z-index: 1;
        }

        .connection.selected {
            stroke: #e74c3c;
            stroke-width: 4;
        }

        .arrow {
            fill: #7f8c8d;
        }

        .arrow.selected {
            fill: #e74c3c;
        }

        .toolbar {
            position: absolute;
            top: 10px;
            left: 10px;
            background: white;
            padding: 10px;
            border-radius: 4px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
            z-index: 100;
        }

        button {
            padding: 5px 10px;
            margin: 0 5px;
            cursor: pointer;
        }


    </style>
</head>
<body>
<div id="container">
    <div class="toolbar">
        <button id="addNode">添加节点</button>
        <button id="deleteSelected">删除选中</button>
        <button id="clearAll">清空画布</button>
    </div>
</div>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        const container = document.getElementById('container');
        const addNodeBtn = document.getElementById('addNode');
        const deleteSelectedBtn = document.getElementById('deleteSelected');
        const clearAllBtn = document.getElementById('clearAll');

        let nodes = [];
        let connections = [];
        let selectedItems = [];
        let isConnecting = false;
        let tempConnection = null;
        let startPort = null;

        // 添加节点按钮
        addNodeBtn.addEventListener('click', function() {
            createNode(100, 100);
        });

        // 删除选中按钮
        deleteSelectedBtn.addEventListener('click', function() {
            deleteSelected();
        });

        // 清空画布按钮
        clearAllBtn.addEventListener('click', function() {
            clearAll();
        });

        // 创建节点
        function createNode(x, y) {
            const node = document.createElement('div');
            node.className = 'node';
            node.style.left = x + 'px';
            node.style.top = y + 'px';
            node.textContent = '节点 ' + (nodes.length + 1);
            node.dataset.id = 'node-' + Date.now();

            // 添加连接点
            const ports = [
                { position: 'top', x: '50%', y: '0%' },
                { position: 'right', x: '100%', y: '50%' },
                { position: 'bottom', x: '50%', y: '100%' },
                { position: 'left', x: '0%', y: '50%' }
            ];

            ports.forEach(port => {
                const portElement = document.createElement('div');
                portElement.className = 'port';
                portElement.style.left = port.x;
                portElement.style.top = port.y;
                portElement.style.transform = 'translate(-50%, -50%)';
                portElement.dataset.position = port.position;
                portElement.dataset.parentId = node.dataset.id;

                // 端口事件处理
                portElement.addEventListener('mousedown', startConnection);
                portElement.addEventListener('mouseup', endConnection);

                node.appendChild(portElement);
            });

            // 节点事件处理
            node.addEventListener('mousedown', selectNode);
            node.addEventListener('dblclick', function() {
                deleteNode(this);
            });

            container.appendChild(node);
            nodes.push(node);

            // 使节点可拖动
            makeDraggable(node);

            return node;
        }

        // 使节点可拖动
        function makeDraggable(node) {
            let offsetX, offsetY;
            let isDragging = false;

            node.addEventListener('mousedown', function(e) {
                if (e.target.classList.contains('port')) return;

                isDragging = true;
                offsetX = e.clientX - node.getBoundingClientRect().left;
                offsetY = e.clientY - node.getBoundingClientRect().top;

                // 选中节点
                if (!e.ctrlKey) {
                    clearSelection();
                }
                selectItem(node);

                document.addEventListener('mousemove', drag);
                document.addEventListener('mouseup', stopDrag);
            });

            function drag(e) {
                if (!isDragging) return;

                const containerRect = container.getBoundingClientRect();
                let newX = e.clientX - containerRect.left - offsetX;
                let newY = e.clientY - containerRect.top - offsetY;

                // 限制在容器范围内
                newX = Math.max(0, Math.min(newX, containerRect.width - node.offsetWidth));
                newY = Math.max(0, Math.min(newY, containerRect.height - node.offsetHeight));

                node.style.left = newX + 'px';
                node.style.top = newY + 'px';

                // 更新所有相关连线
                updateConnectionsForNode(node);
            }

            function stopDrag() {
                isDragging = false;
                document.removeEventListener('mousemove', drag);
                document.removeEventListener('mouseup', stopDrag);
            }
        }

        // 开始创建连线
        function startConnection(e) {
            e.stopPropagation();
            isConnecting = true;
            startPort = e.target;

            // 创建临时连线
            tempConnection = document.createElementNS('http://www.w3.org/2000/svg', 'path');
            tempConnection.className.baseVal = 'connection';
            container.appendChild(createSVG());
            container.querySelector('svg').appendChild(tempConnection);

            document.addEventListener('mousemove', drawTempConnection);
            document.addEventListener('mouseup', cancelConnection);
        }

        // 绘制临时连线
        function drawTempConnection(e) {
            if (!isConnecting || !tempConnection) return;

            const startRect = startPort.getBoundingClientRect();
            const startX = startRect.left + startRect.width/2 - container.getBoundingClientRect().left;
            const startY = startRect.top + startRect.height/2 - container.getBoundingClientRect().top;

            const endX = e.clientX - container.getBoundingClientRect().left;
            const endY = e.clientY - container.getBoundingClientRect().top;

            const path = calculateBezierPath(
                startX, startY,
                getPortDirection(startPort.dataset.position),
                endX, endY,
                getDirectionFromAngle(Math.atan2(endY - startY, endX - startX) * 180 / Math.PI)
            );

            tempConnection.setAttribute('d', path);
        }

        // 完成连线
        function endConnection(e) {
            if (!isConnecting || e.target === startPort) {
                cancelConnection();
                return;
            }

            if (e.target.classList.contains('port')) {
                const endPort = e.target;

                // 创建永久连线
                createConnection(startPort, endPort);
            }

            cancelConnection();
        }

        // 取消连线
        function cancelConnection() {
            isConnecting = false;
            startPort = null;
            if (tempConnection && tempConnection.parentNode) {
                tempConnection.parentNode.removeChild(tempConnection);
            }
            tempConnection = null;
            document.removeEventListener('mousemove', drawTempConnection);
            document.removeEventListener('mouseup', cancelConnection);
        }

        // 创建永久连线
        function createConnection(startPort, endPort) {
            const connectionId = 'conn-' + Date.now();
            const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
            path.className.baseVal = 'connection';
            path.dataset.id = connectionId;

            const arrow = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
            arrow.className.baseVal = 'arrow';
            arrow.setAttribute('points', '0,0 8,4 0,8');
            arrow.dataset.connectionId = connectionId;

            const svg = container.querySelector('svg') || createSVG();
            svg.appendChild(path);
            svg.appendChild(arrow);

            // 存储连线信息
            const connection = {
                id: connectionId,
                startPort: startPort,
                endPort: endPort,
                pathElement: path,
                arrowElement: arrow
            };

            connections.push(connection);

            // 更新连线位置
            updateConnection(connection);

            // 连线点击事件
            path.addEventListener('mousedown', function(e) {
                if (e.ctrlKey) {
                    toggleSelection(connection);
                } else {
                    clearSelection();
                    selectItem(connection);
                }
                e.stopPropagation();
            });

            // 箭头点击事件
            arrow.addEventListener('mousedown', function(e) {
                if (e.ctrlKey) {
                    toggleSelection(connection);
                } else {
                    clearSelection();
                    selectItem(connection);
                }
                e.stopPropagation();
            });

            return connection;
        }

        // 创建SVG容器
        function createSVG() {
            const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            svg.style.position = 'absolute';
            svg.style.top = '0';
            svg.style.left = '0';
            svg.style.width = '100%';
            svg.style.height = '100%';
            svg.style.pointerEvents = 'none';
            container.appendChild(svg);
            return svg;
        }

        // 更新连线位置
        function updateConnection(connection) {
            const startRect = connection.startPort.getBoundingClientRect();
            const endRect = connection.endPort.getBoundingClientRect();

            const startX = startRect.left + startRect.width/2 - container.getBoundingClientRect().left;
            const startY = startRect.top + startRect.height/2 - container.getBoundingClientRect().top;

            const endX = endRect.left + endRect.width/2 - container.getBoundingClientRect().left;
            const endY = endRect.top + endRect.height/2 - container.getBoundingClientRect().top;

            const startDir = getPortDirection(connection.startPort.dataset.position);
            const endDir = getPortDirection(connection.endPort.dataset.position);

            const path = calculateBezierPath(startX, startY, startDir, endX, endY, endDir);
            connection.pathElement.setAttribute('d', path);

            // 更新箭头位置
            const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI;
            connection.arrowElement.setAttribute('transform', `translate(${endX},${endY}) rotate(${angle})`);
        }

        // 更新节点所有相关连线
        function updateConnectionsForNode(node) {
            const nodeId = node.dataset.id;
            connections.forEach(conn => {
                if (conn.startPort.dataset.parentId === nodeId || conn.endPort.dataset.parentId === nodeId) {
                    updateConnection(conn);
                }
            });
        }

        // 计算贝塞尔曲线路径
        function calculateBezierPath(x1, y1, dir1, x2, y2, dir2) {
            const dx = x2 - x1;
            const dy = y2 - y1;
            const dist = Math.sqrt(dx*dx + dy*dy);
            const controlDist = Math.min(dist / 2, 100);

            // 控制点方向向量
            const ctrl1 = getControlPoint(x1, y1, dir1, controlDist);
            const ctrl2 = getControlPoint(x2, y2, dir2, -controlDist);

            return `M${x1},${y1} C${ctrl1.x},${ctrl1.y} ${ctrl2.x},${ctrl2.y} ${x2},${y2}`;
        }

        // 获取控制点
        function getControlPoint(x, y, direction, distance) {
            switch(direction) {
                case 'top': return { x: x, y: y - distance };
                case 'right': return { x: x + distance, y: y };
                case 'bottom': return { x: x, y: y + distance };
                case 'left': return { x: x - distance, y: y };
                default: return { x: x, y: y };
            }
        }

        // 获取端口方向
        function getPortDirection(position) {
            return position; // 'top', 'right', 'bottom', 'left'
        }

        // 根据角度获取方向
        function getDirectionFromAngle(angle) {
            angle = (angle + 360) % 360;
            if (angle >= 45 && angle < 135) return 'bottom';
            if (angle >= 135 && angle < 225) return 'left';
            if (angle >= 225 && angle < 315) return 'top';
            return 'right';
        }

        // 选择节点
        function selectNode(e) {
            if (e.target.classList.contains('port')) return;

            if (e.ctrlKey) {
                toggleSelection(this);
            } else {
                clearSelection();
                selectItem(this);
            }
        }

        // 选择项目
        function selectItem(item) {
            if (item.pathElement) { // 是连线
                item.pathElement.classList.add('selected');
                item.arrowElement.classList.add('selected');
            } else { // 是节点
                item.classList.add('selected');
            }
            selectedItems.push(item);
        }

        // 切换选择状态
        function toggleSelection(item) {
            const index = selectedItems.findIndex(i =>
                i === item || (i.id && item.id && i.id === item.id)
            );

            if (index >= 0) {
                // 取消选择
                if (item.pathElement) {
                    item.pathElement.classList.remove('selected');
                    item.arrowElement.classList.remove('selected');
                } else {
                    item.classList.remove('selected');
                }
                selectedItems.splice(index, 1);
            } else {
                // 选择
                selectItem(item);
            }
        }

        // 清除选择
        function clearSelection() {
            selectedItems.forEach(item => {
                if (item.pathElement) {
                    item.pathElement.classList.remove('selected');
                    item.arrowElement.classList.remove('selected');
                } else {
                    item.classList.remove('selected');
                }
            });
            selectedItems = [];
        }

        // 删除节点
        function deleteNode(node) {
            // 删除相关连线
            const nodeId = node.dataset.id;
            connections = connections.filter(conn => {
                if (conn.startPort.dataset.parentId === nodeId || conn.endPort.dataset.parentId === nodeId) {
                    conn.pathElement.parentNode.removeChild(conn.pathElement);
                    conn.arrowElement.parentNode.removeChild(conn.arrowElement);
                    return false;
                }
                return true;
            });

            // 从节点数组中移除
            nodes = nodes.filter(n => n !== node);
            container.removeChild(node);

            // 从选中项中移除
            selectedItems = selectedItems.filter(item => item !== node);
        }

        // 删除选中项
        function deleteSelected() {
            // 先复制数组，因为删除会影响原数组
            const itemsToDelete = [...selectedItems];

            itemsToDelete.forEach(item => {
                if (item.pathElement) { // 是连线
                    const index = connections.findIndex(conn => conn === item);
                    if (index >= 0) {
                        connections.splice(index, 1);
                        item.pathElement.parentNode.removeChild(item.pathElement);
                        item.arrowElement.parentNode.removeChild(item.arrowElement);
                    }
                } else { // 是节点
                    deleteNode(item);
                }
            });

            selectedItems = [];
        }

        // 清空画布
        function clearAll() {
            nodes.forEach(node => container.removeChild(node));
            connections.forEach(conn => {
                if (conn.pathElement.parentNode) {
                    conn.pathElement.parentNode.removeChild(conn.pathElement);
                }
                if (conn.arrowElement.parentNode) {
                    conn.arrowElement.parentNode.removeChild(conn.arrowElement);
                }
            });

            nodes = [];
            connections = [];
            selectedItems = [];
        }

        // 初始化画布
        function init() {
            // 添加几个示例节点
            const node1 = createNode(100, 100);
            const node2 = createNode(300, 150);
            const node3 = createNode(200, 300);

            // 设置一些示例连线
            setTimeout(() => {
                const ports1 = node1.querySelectorAll('.port');
                const ports2 = node2.querySelectorAll('.port');
                const ports3 = node3.querySelectorAll('.port');

                createConnection(ports1[1], ports2[3]); // node1 right -> node2 left
                createConnection(ports2[2], ports3[0]); // node2 bottom -> node3 top
                createConnection(ports3[1], ports1[2]); // node3 right -> node1 bottom
            }, 100);
        }

        // 初始化
        init();

        // 画布点击事件
        container.addEventListener('mousedown', function(e) {
            if (e.target === container && !e.ctrlKey) {
                clearSelection();
            }
        });
    });
</script>
</body>
</html>